package com.hzw.saas.service.hpc.service;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.hzw.saas.api.hpc.*;
import com.hzw.saas.api.hpc.bo.*;
import com.hzw.saas.api.hpc.enums.HpcAccountStatus;
import com.hzw.saas.api.hpc.enums.HpcTaskStatus;
import com.hzw.saas.api.hpc.query.HpcAccountUserFrozenQuery;
import com.hzw.saas.common.config.annotation.RedisProcessLock;
import com.hzw.saas.common.util.Arith;
import com.hzw.saas.common.util.MapperUtil;
import com.hzw.saas.common.util.SnowFlakeUtil;
import com.hzw.saas.service.hpc.data.HACDetail;
import com.hzw.saas.service.hpc.mapper.HpcAccountRecordMapper;
import com.hzw.saas.service.hpc.model.HpcAccount;
import com.hzw.saas.service.hpc.model.HpcAccountDetail;
import com.hzw.saas.service.hpc.service.impl.HpcAccountServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;

/**
 * <p>
 * 拉取供应商超算账号明细抽象实现
 * 只需要实现 从第三方拉取HPC账户明细逻辑即可
 * </p>
 *
 * @author sonam
 * @since 2021/3/23 15:07
 */
@Slf4j
@RequiredArgsConstructor
public abstract class AbstractHpcFetchService implements IHpcFetchService {
    @Resource(name = "hpcAccountServiceImpl")
    protected IHpcAccountService hpcAccountService;
    @Resource(name = "hpcAccountRecordServiceImpl")
    protected IHpcAccountRecordService hpcAccountRecordService;
    @Resource(name = "hpcAccountUserServiceImpl")
    protected IHpcAccountUserService hpcAccountUserService;
    @Resource(name = "hpcAccountDetailServiceImpl")
    protected IHpcAccountDetailService hpcAccountDetailService;
    @Resource
    private HpcAccountRecordMapper hpcAccountRecordMapper;

    /**
     * 同一时间，一个超算账号只能被一个线程拉取
     */
    @Override
    @RedisProcessLock(lockName = "FetchHpcDetails", key = "#hpcAccountRecordBo.pid")
    public void fetchHpcDetails(HpcAccountRecordBo hpcAccountRecordBo) {
        // 获取HPC帐号开始记录的时间
        Date syncTime = hpcAccountRecordBo.getSyncTime();
        String startDateStr = syncTime == null ? "2020-01-01" : DateUtil.formatDate(syncTime);
        String endDateStr = DateUtil.now();

        // 从并行获取HPC任务明细
        List<HpcAccountDetail> hpcAccountDetails = getHpcAccountDetails(hpcAccountRecordBo, startDateStr, endDateStr);
        log.info("fetch {} in scope time({}~{}) get size {}", hpcAccountRecordBo.getHpcAccountSnapshot(), startDateStr, endDateStr, hpcAccountDetails.size());

        // 计算当前账号超算使用的总核时(startDate ~ endDate)
        double sumHpcCoreTime = hpcAccountDetails.stream().mapToDouble(HpcAccountDetail::getHpcCoreTime).sum();
        sumHpcCoreTime = Arith.div(sumHpcCoreTime, 3600d);
        log.info("拉取账号{}在{}~{}之间使用的核时总数为{}", hpcAccountRecordBo.getHpcAccountSnapshot(), startDateStr, endDateStr, sumHpcCoreTime);

        hpcAccountDetails.forEach(hpcAccountDetail -> hpcAccountDetail.setHpcCoreTime(Arith.div(hpcAccountDetail.getHpcCoreTime(), 3600d)));

        // 将当前拉取到的明细保存到数据库中
        if (saveDetails(hpcAccountDetails)) {
            // 当没有异常抛出时，获取下次startDate，并记录到内存中（每次项目重启都全量拉一次
            if(CollectionUtil.isNotEmpty(hpcAccountDetails)) {
                hpcAccountDetails.sort((a, b) -> DateUtil.compare(a.getStartTime(), b.getStartTime())); // 升序
                Date startTime = new Date();
                for (HpcAccountDetail hpcAccountDetail : hpcAccountDetails) {
                        // 判断EndTime是否为整点，整点说明该任务正在运行或者被截取查询了
                    DateTime dateTime = DateUtil.beginOfDay(hpcAccountDetail.getEndTime());

                    if (DateUtil.compare(hpcAccountDetail.getStartTime(), hpcAccountDetail.getEndTime()) == 0) {
                        continue;
                    }

                    if (DateUtil.compare(dateTime, hpcAccountDetail.getEndTime()) == 0 ||
                        Objects.equals(hpcAccountDetail.getTaskResult(), HpcTaskStatus.RUNNING.code()) ||
                        Objects.equals(hpcAccountDetail.getTaskResult(), HpcTaskStatus.PROGRESS.code())) {
                        // 相等说明该任务为跨天的任务，记录该时间为下次开始记录的时间
                        startTime = hpcAccountDetail.getStartTime();
                        break;
                    }
                }
                // 更新同步时间
                hpcAccountRecordMapper.updateSyncTimeById(hpcAccountRecordBo.getPid(), DateUtil.formatDate(startTime));
            }
        }

    }

    /**
     * 将拉取到的明细保存到数据库中
     */
    protected boolean saveDetails(List<HpcAccountDetail> hpcAccountDetails) {
        List<HpcAccountDetailBo> hpcAccountDetails1 = MapperUtil.nf().mapAsList(hpcAccountDetails, HpcAccountDetailBo.class);
        return hpcAccountDetailService.saveBatchByTaskIdAndAccountIdAndTime(hpcAccountDetails1);
    }

    /**
     * 根据传入的供应商获取账号明细
     */
    @SneakyThrows
    private List<HpcAccountDetail> getHpcAccountDetails(HpcAccountRecordBo hpcAccountRecordBo, String startDateStr, String endDateStr) {
        List<HpcAccountDetail> hpcAccountDetails = new ArrayList<>();
        List<HACDetail> details = fetchHpcDetails(hpcAccountRecordBo, startDateStr, endDateStr);
        String hpcAccountSnapshot = hpcAccountRecordBo.getHpcAccountSnapshot();
        log.info("HPC账户 {} 一共拉取到 {} 条明细", hpcAccountSnapshot, details.size());

        details.forEach(detail -> {
//            if (!Objects.equals(hpcAccountSnapshot, detail.getUser().concat(StrUtil.AT).concat(detail.getCluster())) &&
//                !Objects.equals(hpcAccountSnapshot, detail.getUser())) {
//                return;
//            }

            // 获取超算中心账号对应的业务ID
            Date expireTime = hpcAccountRecordBo.getExpireTime();
            Date createTime = hpcAccountRecordBo.getCreateTime();
            Date submitTime = detail.getSubmitTime();

            // 提交日期在 registerDate与expireDate 之间
            if (DateUtil.compare(expireTime, submitTime) >= 0 &&
                DateUtil.compare(submitTime, createTime) >= 0) {
                String accountId = hpcAccountRecordBo.getPid();

                HpcAccountDetail hpcAccountDetail = new HpcAccountDetail();
                hpcAccountDetail.setDetailId(SnowFlakeUtil.nextIdStr());
                hpcAccountDetail.setTaskId(detail.getJobId());
                hpcAccountDetail.setTaskName(detail.getJobName());
                hpcAccountDetail.setSoftware("-1");
                hpcAccountDetail.setTaskType("");
                hpcAccountDetail.setTaskResult(detail.getStatus());
                HpcTaskStatus taskStatus = HpcTaskStatus.instance(detail.getStatus());
                hpcAccountDetail.setTaskStatus(taskStatus.value());
                hpcAccountDetail.setHpcCore(detail.getAllocCpus()); // 使用的核数
                hpcAccountDetail.setHpcCoreTime(detail.getCputimeRaw()); // 使用的核时
                hpcAccountDetail.setStartTime(detail.getStartTime());
                hpcAccountDetail.setEndTime(detail.getEndTime());
                hpcAccountDetail.setHpcTime(DateUtil.between(detail.getStartTime(), detail.getEndTime(), DateUnit.MS));
                hpcAccountDetail.setRecordTime(new Date());

                // 该超算详细信息绑定 accountId
                hpcAccountDetail.setHpcAccountRecordId(accountId);
                hpcAccountDetails.add(hpcAccountDetail);
            }
        });

        return hpcAccountDetails;
    }

    /**
     * 远程拉取HPC明细，并转为特定的数据格式的HPCDetail
     */
    protected abstract List<HACDetail> fetchHpcDetails(HpcAccountRecordBo hpcAccountRecordBo, String startDateStr, String endDateStr) throws Exception;

    @Override
    @Transactional
    public Double updateHpcAccountBalance(HpcAccountRecordBo hpcAccountRecordBo) {
        /** 统计 账户 的明细数据*/
        Double hpcCoreTime = hpcAccountDetailService.getHpcAccountCoreTimeSum(hpcAccountRecordBo.getPid());
        log.info("账户{}总共使用核时：{}", hpcAccountRecordBo.getPid(), hpcCoreTime);
        /** 更新HPC用户余额*/
        double coreTimeBalance = Arith.sub(hpcAccountRecordBo.getCoreTimeBalanceTotal(), hpcCoreTime);
        hpcAccountRecordMapper.updateCoreTimeBalanceById(hpcAccountRecordBo.getPid(), coreTimeBalance);
        /** 如果HPC用户余额为负数则冻结此账号*/
        if(coreTimeBalance <= 0) {
            String hpcRecordId = hpcAccountRecordBo.getPid();
            try {
                HpcAccountUserBo hpcAccountUserBo = hpcAccountUserService.getMainUserByRecordId(hpcRecordId);
                HpcAccountUserFrozenQuery frozenQuery = new HpcAccountUserFrozenQuery();
                frozenQuery.setHpcAccountStatus(HpcAccountStatus.DISABLED);
                frozenQuery.setHpcAccountUserId(hpcAccountUserBo.getPid());
                hpcAccountUserService.frozenHpcAccount(frozenQuery);
            } catch (Exception e) {
                // 冻结HPC账号异常，不抛出异常
                log.warn(StrUtil.format("冻结HPC账号>>{}<<失败", hpcRecordId), e);
            }
        }
        /** 更新HPC余额*/
        List<String> hpcAccountRecordIds = hpcAccountRecordMapper.getHpcAccountRecordIdByHpcAccountId(hpcAccountRecordBo.getHpcAccountId());
        if(CollectionUtil.isEmpty(hpcAccountRecordIds)) {
            return 0d;
        }
        /** 更新record余额*/
        hpcAccountRecordIds.remove(hpcAccountRecordBo.getPid());
        double sumHpcCoreTime;
        if(hpcAccountRecordIds.size() == 1) {
            sumHpcCoreTime = Arith.add(hpcCoreTime, hpcAccountDetailService.getHpcAccountCoreTimeSum(hpcAccountRecordIds.get(0)));
        } else {
            sumHpcCoreTime = Arith.add(hpcCoreTime, hpcAccountDetailService.getHpcAccountCoreTimeSums(hpcAccountRecordIds));
        }
        HpcAccountBo hpcAccountBo = hpcAccountService.getById(hpcAccountRecordBo.getHpcAccountId());
        if(Objects.nonNull(hpcAccountBo)) {
            hpcAccountBo.setCoreTimeAvailable(Arith.sub(hpcAccountBo.getCoreTimeAvailableTotal(), sumHpcCoreTime));
            ((HpcAccountServiceImpl)hpcAccountService).updateById(MapperUtil.nf().map(hpcAccountBo, HpcAccount.class));
        }
        return hpcCoreTime;
    }

    @Override
    public HpcAccountAmountDailyBo recordHpcBalance(HpcAccountRecordBo hpcAccountRecordBo) {
        return hpcAccountRecordService.recordHpcBalanceByDate(hpcAccountRecordBo.getPid(), DateUtil.offsetDay(DateUtil.date(), -1));
    }
}
